/*
 * \brief  Test for the repeated child creation in a dynamic init
 * \author Norman Feske
 * \date   2018-06-07
 */

/*
 * Copyright (C) 2018 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#include <base/heap.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/session_state.h>
#include <log_session/log_session.h>
#include <os/reporter.h>

namespace Test {
	struct Main;
	using namespace Genode;
}


struct Test::Main
{
	Env &_env;

	bool _client_starting = false;

	Expanding_reporter _init_config_reporter { _env, "config",  "init.config" };

	void _gen_log_server_start_content(Generator &g) const
	{
		g.attribute("name", "server");
		g.attribute("caps", "100");
		g.node("resource", [&] () {
			g.attribute("name", "RAM");
			g.attribute("quantum", "1M"); });

		g.node("binary", [&] () {
			g.attribute("name", "dummy"); });

		g.node("config", [&] () {
			g.node("log_service", [&] () {}); });

		g.node("provides", [&] () {
			g.node("service", [&] () {
				g.attribute("name", Log_session::service_name()); }); });

		g.node("route", [&] () {
			g.node("any-service", [&] () {
				g.node("parent", [&] () {}); }); });
	}

	void _gen_log_client_start_content(Generator &g) const
	{
		g.attribute("name", "client");
		g.attribute("caps", "200");
		g.node("resource", [&] () {
			g.attribute("name", "RAM");
			g.attribute("quantum", "1M"); });

		g.node("binary", [&] () {
			g.attribute("name", "dummy"); });

		g.node("config", [&] () {
			g.node("create_log_connections", [&] () {
				g.attribute("count", 1);
				g.attribute("ram_upgrade", "64K");
			});
			g.node("log", [&] () {
				g.attribute("string", "client started"); }); });

		g.node("route", [&] () {

			g.node("service", [&] () {
				g.attribute("name", Log_session::service_name());
				g.node("child", [&] () {
					g.attribute("name", "server"); });
			});

			g.node("any-service", [&] () {
				g.node("parent", [&] () {}); });
		});
	}

	void _gen_init_config(Generator &g) const
	{
		g.node("report", [&] () {
			g.attribute("requested",  "yes");
			g.attribute("provided",   "yes");
			g.attribute("init_ram",   "yes");
			g.attribute("init_caps",  "yes");
			g.attribute("child_ram",  "yes");
			g.attribute("child_caps", "yes");
			g.attribute("delay_ms",    100);
		});

		auto gen_service = [&] (char const *name) {
			g.node("service", [&] () { g.attribute("name", name); }); };

		g.node("parent-provides", [&] () {
			gen_service("ROM");
			gen_service("CPU");
			gen_service("PD");
			gen_service("RM");
			gen_service("LOG");
		});

		g.node("start", [&] () {
			_gen_log_server_start_content(g); });

		if (_client_starting)
			g.node("start", [&] () {
				_gen_log_client_start_content(g); });
	}

	void generate_init_config()
	{
		_init_config_reporter.generate([&] (Generator &g) {
			_gen_init_config(g); });
	}

	/*
	 * Handling of state reports generated by init
	 */
	Attached_rom_dataspace _init_state { _env, "state" };

	Signal_handler<Main> _init_state_handler {
		_env.ep(), *this, &Main::_handle_init_state };

	/* counter for iterations */
	unsigned _cnt = 0;

	struct Ram_tracker
	{
		using Name = String<32>;

		Name const _name;

		/* RAM quota of previous iteration */
		size_t _previous = 0;

		size_t total_loss = 0;

		void update(Node const &ram)
		{
			size_t const current = ram.attribute_value("quota", Number_of_bytes());

			log(_name, " RAM: ", Number_of_bytes(current));
			if (_previous) {

				if (current < _previous) {
					total_loss += _previous - current;
					log(_name, " lost ", _previous - current, " bytes", " "
					    "(total ", total_loss, " bytes)");
				}

				if (current > _previous)
					log(_name, " gained ", current - _previous, " bytes");
			}
			_previous = current;
		}

		Ram_tracker(Name const &name) : _name(name) { }
	};

	Ram_tracker _init_ram_tracker   { "init" };
	Ram_tracker _server_ram_tracker { "server" };

	static Number_of_bytes _init_ram(Node const &state)
	{
		return state.with_sub_node("ram",
			[&] (Node const &ram) {
				return ram.attribute_value("quota", Number_of_bytes()); },
			[&] { return Number_of_bytes(); });
	}

	using Name = String<32>;

	template <typename FN>
	void _apply_child(Node const &state, Name const &name, FN const &fn)
	{
		state.for_each_sub_node("child", [&] (Node const &child) {
			if (child.attribute_value("name", Name()) == name)
				fn(child); });
	}

	void _handle_init_state()
	{
		_init_state.update();

		Node const &state = _init_state.node();

		/*
		 * Detect state where the client is running and has established a
		 * session to the LOG server.
		 */
		bool client_present  = false;
		bool client_complete = false;
		_apply_child(state, "client", [&] (Node const &child) {
			client_present = true;
			child.for_each_sub_node("requested", [&] (Node const &requested) {
				requested.for_each_sub_node("session", [&] (Node const &session) {
					if (session.attribute_value("service", String<16>()) == "LOG"
					 && session.attribute_value("state", String<16>()) == "CAP_HANDED_OUT")
						client_complete = true; }); }); });

		bool client_connected = false;
		_apply_child(state, "server", [&] (Node const &child) {
			child.for_each_sub_node("provided", [&] (Node const &provided) {
				client_connected |= (provided.num_sub_nodes() > 0); }); });

		if (_client_starting) {

			/* kill client as soon as it started up completely */
			if (client_complete)
				_client_starting = false;

		} else {

			/* restart client as soon as it vanished */
			if (!client_present && !client_connected) {

				_cnt++;
				log("iteration ", _cnt);

				state.with_optional_sub_node("ram", [&] (Node const &ram) {
					_init_ram_tracker.update(ram); });

				_apply_child(state, "server", [&] (Node const &child) {
					child.with_optional_sub_node("ram", [&] (Node const &ram) {
						_server_ram_tracker.update(ram); }); });

				_client_starting = true;

				if (_init_ram_tracker.total_loss   > 16*1024
				 || _server_ram_tracker.total_loss > 16*1024) {

					error("unexpected quota distribution");
					_env.parent().exit(1);
				}
			}
		}

		/* success after 20 iterations without any accounting issues */
		if (_cnt == 20)
			_env.parent().exit(0);

		generate_init_config();
	}

	Main(Env &env) : _env(env)
	{
		_init_state.sigh(_init_state_handler);

		_handle_init_state();

		generate_init_config();
	}
};


void Component::construct(Genode::Env &env) { static Test::Main main(env); }

